<template>
	<div>
		<span :style="getColor()">进度:{{ finishNum }}/{{ rangeDownload.targetSegment }},错误:{{ errorNum }}</span>
		<span class="m-p-retry" v-if="errorNum && downloadIndex >= rangeDownload.targetSegment" @click="retryAll">错误重下</span>
	</div>
</template>

<script>
let muxjs = window._CONFIG['muxjs'];
let AESDecryptor = window._CONFIG['AESDecryptor'];
export default {
	name: 'aaa',
	data() {
		return {
			url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-uni4934e7b/c4d93960-5643-11eb-a16f-5b3e54966275.m3u8', // 在线链接
			tips: 'm3u8 视频在线提取工具', // 顶部提示
			isPause: false, // 是否暂停下载
			isGetMP4: false, // 是否转码为 MP4 下载
			durationSecond: 0, // 视频持续时长
			isShowRefer: true, // 是否显示推送
			downloading: false, // 是否下载中
			beginTime: '', // 开始下载的时间
			errorNum: 0, // 错误数
			finishNum: 0, // 已下载数
			downloadIndex: 0, // 当前下载片段
			finishList: [], // 下载完成项目
			tsUrlList: [], // ts URL数组
			mediaFileList: [], // 下载的媒体数组
			rangeDownload: { // 特定范围下载
				isShowRange: false, // 是否显示范围下载
				startSegment: '', // 起始片段
				endSegment: '', // 截止片段
				targetSegment: 1, // 待下载片段
			},
			aesConf: { // AES 视频解密配置
				method: '', // 加密算法
				uri: '', // key 所在文件路径
				iv: '', // 偏移值
				key: '', // 秘钥
				decryptor: null, // 解码器对象
				stringToBuffer: function (str) {
					return new TextEncoder().encode(str)
				},
			},
		};
	},
	props: {
		record:{
			type: Object,
			default: null
		}
	},
	computed: {

	},
	created() {

	},
	methods: {
		// 获取链接中携带的资源链接
		getSource() {
			let { href } = location
			if (href.indexOf('?source=') > -1) {
				this.url = href.split('?source=')[1]
			}
		},
		
		
		// ajax 请求
		ajax(options) {
			options = options || {};
			let xhr = new XMLHttpRequest();
			if (options.type === 'file') {
				xhr.responseType = 'arraybuffer';
			}
					
			xhr.onreadystatechange = function () {
				if (xhr.readyState === 4) {
					let status = xhr.status;
					if (status >= 200 && status < 300) {
						options.success && options.success(xhr.response);
					} else {
						options.fail && options.fail(status);
					}
				}
			};
					
			xhr.open("GET", options.url, true);
			xhr.send(null);
		},
		
		// 合成URL
		applyURL(targetURL, baseURL) {
			baseURL = baseURL || location.href
			if (targetURL.indexOf('http') === 0) {
				return targetURL
			} else if (targetURL[0] === '/') {
				let domain = baseURL.split('/')
				return domain[0] + '//' + domain[2] + targetURL
			} else {
				let domain = baseURL.split('/')
				domain.pop()
				return domain.join('/') + '/' + targetURL
			}
		},
		
		// 解析为 mp4 下载
		getMP4() {
			this.isGetMP4 = true
			this.getM3U8()
		},
		
		// 获取在线文件
		getM3U8(onlyGetRange) {
			if (!this.url) {
				alert('请输入链接')
				return
			}
			if (this.url.toLowerCase().indexOf('.m3u8') === -1) {
				alert('链接有误，请重新输入')
				return
			}
			if (this.downloading) {
				alert('资源下载中，请稍后')
				return
			}
			
		
			// 在下载页面才触发，修改页面 URL，携带下载路径，避免刷新后丢失
			
		
			this.tips = 'm3u8 文件下载中，请稍后'
			this.beginTime = new Date()
			this.ajax({
				url: this.url,
				success: (m3u8Str) => {
					this.tsUrlList = []
					this.finishList = []
					
					// 提取 ts 视频片段地址
					m3u8Str.split('\n').forEach((item) => {
						if (item.toLowerCase().indexOf('.ts') > -1 || item.toLowerCase().indexOf('.image') > -1) {
							this.tsUrlList.push(this.applyURL(item, this.url))
							this.finishList.push({
								title: item,
								status: ''
							})
						}
					})
					
					// 仅获取视频片段数
					if (onlyGetRange) {
						this.rangeDownload.isShowRange = true
						this.rangeDownload.endSegment = this.tsUrlList.length
						this.rangeDownload.targetSegment = this.tsUrlList.length
						return
					} else {
						let startSegment = Math.max(this.rangeDownload.startSegment || 1, 1) // 最小为 1
						let endSegment = Math.max(this.rangeDownload.endSegment || this.tsUrlList.length, 1)
						startSegment = Math.min(startSegment, this.tsUrlList.length) // 最大为 this.tsUrlList.length
						endSegment = Math.min(endSegment, this.tsUrlList.length)
						this.rangeDownload.startSegment = Math.min(startSegment, endSegment)
						this.rangeDownload.endSegment = Math.max(startSegment, endSegment)
						this.rangeDownload.targetSegment = this.rangeDownload.endSegment - this.rangeDownload.startSegment + 1
						this.downloadIndex = this.rangeDownload.startSegment - 1
						this.downloading = true
					}
					
					// 获取需要下载的 MP4 视频长度
					if (this.isGetMP4) {
						let infoIndex = 0
						m3u8Str.split('\n').forEach(item => {
							if (item.toUpperCase().indexOf('#EXTINF:') > -1) { // 计算视频总时长，设置 mp4 信息时使用
								infoIndex++
								if (this.rangeDownload.startSegment <= infoIndex && infoIndex <= this.rangeDownload.endSegment) {
									this.durationSecond += parseFloat(item.split('#EXTINF:')[1])
								}
							}
						})
					}
					
					// 检测视频 AES 加密
					if (m3u8Str.indexOf('#EXT-X-KEY') > -1) {
						this.aesConf.method = (m3u8Str.match(/(.*METHOD=([^,\s]+))/) || ['', '', ''])[2]
						this.aesConf.uri = (m3u8Str.match(/(.*URI="([^"]+))"/) || ['', '', ''])[2]
						this.aesConf.iv = (m3u8Str.match(/(.*IV=([^,\s]+))/) || ['', '', ''])[2]
						this.aesConf.iv = this.aesConf.iv ? this.aesConf.stringToBuffer(this.aesConf.iv) : ''
						this.aesConf.uri = this.applyURL(this.aesConf.uri, this.url)
					
						// let params = m3u8Str.match(/#EXT-X-KEY:([^,]*,?METHOD=([^,]+))?([^,]*,?URI="([^,]+)")?([^,]*,?IV=([^,^\n]+))?/)
						// this.aesConf.method = params[2]
						// this.aesConf.uri = this.applyURL(params[4], this.url)
						// this.aesConf.iv = params[6] ? this.aesConf.stringToBuffer(params[6]) : ''
						this.getAES();
					} else if (this.tsUrlList.length > 0) { // 如果视频没加密，则直接下载片段，否则先下载秘钥
						this.downloadTS()
						// this.getAES();
					} else {
						this.alertError('资源为空，请查看链接是否有效')
					}
				},
				fail: () => {
					this.alertError('链接不正确，请查看链接是否有效')
				}
			})
		},
		
		// 获取AES配置
		getAES() {
			alert('视频被 AES 加密，点击确认，进行视频解码')
			this.ajax({
				type: 'file',
				url: this.aesConf.uri,
				success: (key) => {
					// console.log('getAES', key)
					// this.aesConf.key = this.aesConf.stringToBuffer(key)
					this.aesConf.key = key
					this.aesConf.decryptor = new AESDecryptor()
					this.aesConf.decryptor.constructor()
					this.aesConf.decryptor.expandKey(this.aesConf.key);
					this.downloadTS()
				},
				fail: () => {
					this.alertError('视频已进行定制化加密，不提供定制化解密下载')
				}
			})
		},
		
		// ts 片段的 AES 解码
		aesDecrypt(data, index) {
			let iv = this.aesConf.iv || new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, index])
			return this.aesConf.decryptor.decrypt(data, 0, iv.buffer || iv, true)
		},
		
		// 下载分片
		downloadTS() {
			this.tips = 'ts 视频碎片下载中，请稍后'
			let download = () => {
				let isPause = this.isPause // 使用另一个变量来保持下载前的暂停状态，避免回调后没修改
				let index = this.downloadIndex
				console.log('url',this.tsUrlList[index])
				this.downloadIndex++
				if (this.finishList[index] && this.finishList[index].status === '') {
					this.ajax({
						url: this.tsUrlList[index],
						type: 'file',
						success: (file) => {
							this.dealTS(file, index, () => this.downloadIndex < this.rangeDownload.endSegment && !isPause && download())
						},
						fail: () => {
							this.errorNum++
							this.finishList[index].status = 'error'
							if (this.downloadIndex < this.rangeDownload.endSegment) {
								!isPause && download()
							}
						}
					})
				} else if (this.downloadIndex < this.rangeDownload.endSegment) { // 跳过已经成功的片段
					!isPause && download()
				}
			}
					
			// 建立多少个 ajax 线程
			for (let i = 0; i < Math.min(10, this.rangeDownload.targetSegment - this.finishNum); i++) {
				download(i)
			}
		},
		
		// 处理 ts 片段，AES 解密、mp4 转码
		dealTS(file, index, callback) {
			const data = this.aesConf.uri ? this.aesDecrypt(file, index) : file
			this.conversionMp4(data, index, (afterData) => { // mp4 转码
				this.mediaFileList[index - this.rangeDownload.startSegment + 1] = afterData // 判断文件是否需要解密
				this.finishList[index].status = 'finish'
				this.finishNum++
				if (this.finishNum === this.rangeDownload.targetSegment) {
					this.downloadFile(this.mediaFileList, this.formatTime(this.beginTime, 'YYYY_MM_DD hh_mm_ss'))
				}
				callback && callback()
			})
		},
		
		// 转码为 mp4
		conversionMp4(data, index, callback) {
			if (this.isGetMP4) {
				let transmuxer = new muxjs.Transmuxer({
					keepOriginalTimestamps: true,
					duration: parseInt(this.durationSecond),
				});
				transmuxer.on('data', segment => {
					if (index === this.rangeDownload.startSegment - 1) {
						let data = new Uint8Array(segment.initSegment.byteLength + segment.data.byteLength);
						data.set(segment.initSegment, 0);
						data.set(segment.data, segment.initSegment.byteLength);
						callback(data.buffer)
					} else {
						callback(segment.data)
					}
				})
				transmuxer.push(new Uint8Array(data));
				transmuxer.flush();
			} else {
				callback(data)
			}
		},
		
		// 暂停与恢复
		togglePause() {
			this.isPause = !this.isPause
			!this.isPause && this.retryAll()
		},
		
		// 重新下载某个片段
		retry(index) {
			if (this.finishList[index].status === 'error') {
				this.finishList[index].status = ''
				this.ajax({
					url: this.tsUrlList[index],
					type: 'file',
					success: (file) => {
						this.errorNum--
						this.dealTS(file, index)
					},
					fail: () => {
						this.finishList[index].status = 'error'
					}
				})
			}
		},
		
		// 重新下载所有错误片段
		retryAll() {
			this.finishList.forEach((item) => { // 重置所有片段状态
				if (item.status === 'error') {
					item.status = ''
				}
			})
			this.errorNum = 0
			this.downloadIndex = this.rangeDownload.startSegment - 1
			this.downloadTS()
		},
		
		// 下载整合后的TS文件
		downloadFile(fileDataList, fileName) {
			this.tips = 'ts 碎片整合中，请留意浏览器下载'
			let fileBlob = null
			let a = document.createElement('a')
			if (this.isGetMP4) {
				fileBlob = new Blob(fileDataList, { type: 'video/mp4' }) // 创建一个Blob对象，并设置文件的 MIME 类型
				a.download = fileName + '.mp4'
			} else {
				fileBlob = new Blob(fileDataList, { type: 'video/MP2T' }) // 创建一个Blob对象，并设置文件的 MIME 类型
				a.download = fileName + '.ts'
			}
			a.href = URL.createObjectURL(fileBlob)
			a.style.display = 'none'
			document.body.appendChild(a)
			a.click()
			a.remove()
			
			setTimeout(()=>{
				this.$message.success('ts 碎片整合中，请留意浏览器下载');
				this.record.showDownload = false;
			},3000)
		},
		
		// 格式化时间
		formatTime(date, formatStr) {
			const formatType = {
				Y: date.getFullYear(),
				M: date.getMonth() + 1,
				D: date.getDate(),
				h: date.getHours(),
				m: date.getMinutes(),
				s: date.getSeconds(),
			}
			return formatStr.replace(
				/Y+|M+|D+|h+|m+|s+/g,
				target => (new Array(target.length).join('0') + formatType[target[0]]).substr(-target.length)
			)
		},
		
		// 强制下载现有片段
		forceDownload() {
			if (this.mediaFileList.length) {
				this.downloadFile(this.mediaFileList, this.formatTime(this.beginTime, 'YYYY_MM_DD hh_mm_ss'))
			} else {
				alert('当前无已下载片段')
			}
		},
		
		// 发生错误，进行提示
		alertError(tips) {
			alert(tips)
			this.downloading = false
			this.tips = 'm3u8 视频在线提取工具';
		},
		
		getColor() {
			if(this.errorNum && this.downloadIndex >= this.rangeDownload.targetSegment){
				return "color: red"
			}
			else if(this.finishNum == this.rangeDownload.targetSegment){
				return "color: #0acd76" // 10 205 118
			}
			else {//return "color: #bf7732" //191 119 50
				let r = 191 + parseInt(((10 - 191) * this.finishNum / this.rangeDownload.targetSegment ).toFixed())
				let g = 119 + parseInt(((205 - 119) * this.finishNum / this.rangeDownload.targetSegment ).toFixed())
				let b = 50 + parseInt(((118 - 50) * this.finishNum / this.rangeDownload.targetSegment ).toFixed())
				return "color: rgb(" + r + "," + g + "," + b + ")"
			}
		}

	},
	beforeDestroy() {
		console.log('--------beforeDestroy------');
		this.isPause = true
	},
	mounted() {
		console.log('--------download mounted------',this.record);
		this.url = this.record.recordUrl
		console.log(this.url,'=====')
		// this.getMP4();//mp4时长有问题
		this.getM3U8(false);//
	}
};
</script>

<style lang="less" scoped>
.m-p-retry {
	position: absolute;
	right: 44px;
	display: inline-block;
	font-size: 14px;
	color: #40a9ff;
	cursor: pointer;
	border-radius: 4px;
	border: 1px solid #40a9ff;
	// background-color: #3D8AC7;
	opacity: 1;
	transition: 0.3s all;
}
.m-p-retry:hover {
  opacity: 0.9;
}
</style>
